/**
 * Copyright(c) Live2D Inc. All rights reserved.
 *
 * Use of this source code is governed by the Live2D Open Software license
 * that can be found at https://www.live2d.com/eula/live2d-open-software-license-agreement_en.html.
 */

export namespace Live2DCubismFramework
{
    /**
     * 4x4の行列
     *
     * 4x4行列の便利クラス。
     */
    export class CubismMatrix44
    {
        /**
         * コンストラクタ
         */
        public constructor()
        {
            this._tr = new Float32Array(16);    // 4 * 4のサイズ
            this.loadIdentity();
        }

        /**
         * 受け取った２つの行列の乗算を行う。
         *
         * @param a 行列a
         * @param b 行列b
         * @return 乗算結果の行列
         */
        public static multiply(a: Float32Array, b: Float32Array, dst: Float32Array): void
        {
            let c: Float32Array = new Float32Array(
                [
                    0.0, 0.0, 0.0, 0.0,
                    0.0, 0.0, 0.0, 0.0,
                    0.0, 0.0, 0.0, 0.0,
                    0.0, 0.0, 0.0, 0.0
                ]
            );

            let n: number = 4;

            for (let i: number = 0; i < n; ++i)
            {
                for(let j: number = 0; j < n; ++j)
                {
                    for(let k: number = 0; k < n; ++k)
                    {
                        c[j + i * 4] += a[k + i * 4] * b[j + k * 4];
                    }
                }
            }

            for(let i: number = 0; i < 16; ++i)
            {
                dst[i] = c[i];
            }
        }

        /**
         * 単位行列に初期化する
         */
        public loadIdentity(): void
        {
            let c: Float32Array = new Float32Array(
                [
                    1.0, 0.0, 0.0, 0.0,
                    0.0, 1.0, 0.0, 0.0,
                    0.0, 0.0, 1.0, 0.0,
                    0.0, 0.0, 0.0, 1.0
                ]
            );

            this.setMatrix(c);
        }

        /**
         * 行列を設定
         *
         * @param tr 16個の浮動小数点数で表される4x4の行列
         */
        public setMatrix(tr: Float32Array): void
        {
            for(let i = 0; i < 16; ++i)
            {
                this._tr[i] = tr[i];
            }
        }

        /**
         * 行列を浮動小数点数の配列で取得
         *
         * @return 16個の浮動小数点数で表される4x4の行列
         */
        public getArray(): Float32Array
        {
            return this._tr;
        }

        /**
         * X軸の拡大率を取得
         * @return X軸の拡大率
         */
        public getScaleX(): number
        {
            return this._tr[0];
        }

        /**
         * Y軸の拡大率を取得する
         *
         * @return Y軸の拡大率
         */
        public getScaleY(): number
        {
            return this._tr[5];
        }

        /**
         * X軸の移動量を取得
         * @return X軸の移動量
         */
        public getTranslateX(): number
        {
            return this._tr[12];
        }

        /**
         * Y軸の移動量を取得
         * @return Y軸の移動量
         */
        public getTranslateY(): number
        {
            return this._tr[13];
        }

        /**
         * X軸の値を現在の行列で計算
         *
         * @param src X軸の値
         * @return 現在の行列で計算されたX軸の値
         */
        public transformX(src: number): number
        {
            return this._tr[0] * src + this._tr[12];
        }

        /**
         * Y軸の値を現在の行列で計算
         *
         * @param src Y軸の値
         * @return　現在の行列で計算されたY軸の値
         */
        public transformY(src: number): number
        {
            return this._tr[5] * src + this._tr[13];
        }

        /**
         * X軸の値を現在の行列で逆計算
         */
        public invertTransformX(src: number): number
        {
            return (src - this._tr[12]) / this._tr[0];
        }

        /**
         * Y軸の値を現在の行列で逆計算
         */
        public invertTransformY(src: number): number
        {
            return (src - this._tr[13]) / this._tr[5];
        }

        /**
         * 現在の行列の位置を起点にして移動
         *
         * 現在の行列の位置を起点にして相対的に移動する。
         *
         * @param x X軸の移動量
         * @param y Y軸の移動量
         */
        public translateRelative(x: number, y: number): void
        {
            let tr1: Float32Array = new Float32Array(
                [
                    1.0,    0.0,    0.0,    0.0,
                    0.0,    1.0,    0.0,    0.0,
                    0.0,    0.0,    1.0,    0.0,
                    x,      y,      0.0,    1.0
                ]
            );

            CubismMatrix44.multiply(tr1, this._tr, this._tr);
        }

        /**
         * 現在の行列の位置を移動
         *
         * 現在の行列の位置を指定した位置へ移動する
         *
         * @param x X軸の移動量
         * @param y y軸の移動量
         */
        public translate(x: number, y: number): void
        {
            this._tr[12] = x;
            this._tr[13] = y;
        }

        /**
         * 現在の行列のX軸の位置を指定した位置へ移動する
         *
         * @param x X軸の移動量
         */
        public translateX(x: number): void
        {
            this._tr[12] = x;
        }

        /**
         * 現在の行列のY軸の位置を指定した位置へ移動する
         *
         * @param y Y軸の移動量
         */
        public translateY(y: number): void
        {
            this._tr[13] = y;
        }


        /**
         * 現在の行列の拡大率を相対的に設定する
         *
         * @param x X軸の拡大率
         * @param y Y軸の拡大率
         */
        public scaleRelative(x: number, y:number): void
        {
            let tr1: Float32Array = new Float32Array(
                [
                    x,      0.0,    0.0,    0.0,
                    0.0,    y,      0.0,    0.0,
                    0.0,    0.0,    1.0,    0.0,
                    0.0,    0.0,    0.0,    1.0
                ]
            );

            CubismMatrix44.multiply(tr1, this._tr, this._tr);
        }

        /**
         * 現在の行列の拡大率を指定した倍率に設定する
         *
         * @param x X軸の拡大率
         * @param y Y軸の拡大率
         */
        public scale(x: number, y: number): void
        {
            this._tr[0] = x;
            this._tr[5] = y;
        }

        /**
         * 現在の行列に行列を乗算
         *
         * @param m 行列
         */
        public multiplyByMatrix(m: CubismMatrix44): void
        {
            CubismMatrix44.multiply(m.getArray(), this._tr, this._tr);
        }

        /**
         * オブジェクトのコピーを生成する
         */
        public clone(): CubismMatrix44
        {
            let cloneMatrix: CubismMatrix44 = new CubismMatrix44();

            for(let i: number = 0; i < this._tr.length; i++)
            {
                cloneMatrix._tr[i] = this._tr[i];
            }

            return cloneMatrix;
        }

        protected _tr: Float32Array;    // 4x4行列データ
    }
}
